package edu.mse.camel.client.posterboard.ui.emflistener;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.change.ChangeDescription;
import org.eclipse.emf.ecore.change.FeatureChange;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.Resource.IOWrappedException;
import org.eclipse.emf.ecore.util.EObjectEList;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.uml2.uml.Model;

import edu.mse.camel.client.core.events.model.ModelUpdateEvent2;
import edu.mse.camel.client.core.events.model.ModelUpdateEvent2.KIND;
import edu.mse.camel.client.eventbus.pubsub.EventManager;
import edu.mse.camel.client.events.peer.SendGenericPeerEvent;
import edu.mse.camel.client.log.Debug;
import edu.mse.camel.client.posterboard.ui.PosterboardPlugin;

public class CamelChangeRecorder2 {
	
	protected ResourceSet root; 
	protected UMLChangeRecorder delegate;
		
	public CamelChangeRecorder2(ResourceSet root) {
		this.delegate = new UMLChangeRecorder();
		this.root = root;
	}
	
	public boolean IsRecording () {
		return delegate.isRecording();
	}
	
	private void setDelivery(boolean deliver) {
		// the resourseSet itself is also a notifier
		root.eSetDeliver(deliver);
		for(Resource res : root.getResources()) {
			TreeIterator<EObject> tree = 
				EcoreUtil.getAllContents(Collections.singleton(res));
			while (tree.hasNext()) {
				Object obj = tree.next();
				if(obj instanceof Notifier) {
					Notifier notifier = (Notifier) obj;
					notifier.eSetDeliver(deliver);
				}
			}
		}
	}
	
	public void publish(int serialNumber, String cmdName) {
		Resource cdResource = null;
		try {
			ChangeDescription cd = delegate.endRecording();
			
			// it is important to disable notification for many elements first to stop unnecessary notifications
			// (currently we do not know the safe minimal set to disable, so we just disable them all)
			// the creation and disposal of a resource would generate a lot of notifications !
			// Papyrus itself has a lot of adaptors hooked, and we do not want them to be alerted by the "apply/apply-and-reverse" trick
			setDelivery(false);
			
			cdResource = root.createResource(root.getResources().get(1).getURI());
			
			DumpReverseEvent(cdResource, cd);
			
			if (cd != null) {
				cd.applyAndReverse();
				publishEvent(cdResource, cd, serialNumber, cmdName);
				cd.apply();
			}
			
		} catch (IOWrappedException iowe) {
			iowe.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(cdResource != null) {
				root.getResources().remove(cdResource);
			}
			
			// we have to re-enable notification so Papyrus can continue properly
			setDelivery(true);
		}
	}
	
	static int sequence = 0;
	static boolean dumpChanges = false;
	@SuppressWarnings("unchecked")
	private void DumpReverseEvent(Resource cdResource, ChangeDescription cd)
			throws IOException {
		
		if(dumpChanges == false) {
			return;
		}
		
		cdResource.getContents().add(cd);
		
		Map option = new HashMap();
		option.put(XMLResource.OPTION_PROCESS_DANGLING_HREF,
				XMLResource.OPTION_PROCESS_DANGLING_HREF_RECORD);
		cdResource.save(new FileOutputStream(new File("c:\\temp\\reverse_" + sequence + ".xml")), option);

		cdResource.getContents().remove(cd);
	}
	
	private EList<Map.Entry<EObject, EList<FeatureChange>>> attachDanglingKeyObjects (ChangeDescription cd) {
		EList<Map.Entry<EObject, EList<FeatureChange>>> attachedEntry = 
			new EObjectEList<Map.Entry<EObject, EList<FeatureChange>>>(EObject.class, (InternalEObject) cd, 0);
		
	    for (Map.Entry<EObject, EList<FeatureChange>> entry : cd.getObjectChanges())
	    {
	      EObject keyObject = entry.getKey();
	      
	      String fragment = EcoreUtil.getURI(keyObject).fragment();
	      
	      Debug.log_info(PosterboardPlugin.PLUGIN_ID, "key object : " + keyObject + " with uri fragment " + fragment);

	      if("//".equals(fragment)) {
	    	  //dangling object
	    	  attachedEntry.add(entry);
	    	  
	    	  Debug.log_info(PosterboardPlugin.PLUGIN_ID, "****** attach to destination ******");
	      }
	    }
	    
	    for(Map.Entry<EObject, EList<FeatureChange>> entry : attachedEntry) {
	    	cd.getObjectsToAttach().add(entry.getKey());
	    }
	    return attachedEntry;
	}

	@SuppressWarnings("unchecked")
	private void publishEvent(Resource cdResource, ChangeDescription cd, int serialNumber, String cmdName)
			throws IOException {
		
		EList<Map.Entry<EObject, EList<FeatureChange>>> attachedEntry = attachDanglingKeyObjects(cd);
		
		cdResource.getContents().add(cd);
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		
		Map option = new HashMap();
		option.put(XMLResource.OPTION_PROCESS_DANGLING_HREF,
				XMLResource.OPTION_PROCESS_DANGLING_HREF_RECORD);
		
		cdResource.save(baos, option);
		
		if(dumpChanges == true) {
			cdResource.save(new FileOutputStream(new File("c:\\temp\\sender_" + sequence + ".xml")), option);
			sequence++;
		}
		
	    for(Map.Entry<EObject, EList<FeatureChange>> entry : attachedEntry) {
	    	cd.getObjectsToAttach().remove(entry.getKey());
	    }
		
		ModelUpdateEvent2 me2 = new ModelUpdateEvent2(KIND.UML);
		me2.setByteArray(baos.toByteArray());
		me2.setCmdName(cmdName);
		me2.setSerialNumber(serialNumber);
		SendGenericPeerEvent sent = new SendGenericPeerEvent();
		sent.writeObject(me2);
		EventManager.getInstance().dispatchEventAsynchronous(sent, true);
		Debug.log_info(PosterboardPlugin.PLUGIN_ID, "sending out cmd " + me2.getCmdName() + " [" + me2.getSerialNumber() + "]");
	}
	
	/*
	protected ModelUpdateEvent2.KIND getKind() {
		if (root instanceof Model) {
			return ModelUpdateEvent2.KIND.UML;
		} else {
			return ModelUpdateEvent2.KIND.DI2;
		}
	}
	*/

	public void beginRecording() {
		delegate.beginRecording(Collections.singleton(root));
	}

	public void dispose() {
		delegate.dispose();
	}

}
